package p3.ejemplos;

import java.io.*;
import java.net.*;
import java.util.LinkedList;
import java.util.List;

import p3.IHandler;
import p3.IServidor;

/**
 * 
 * Utiliza una pila de hilos para servir a los clientes
 * @author JAPF
 *
 */
public class ServidorPilaDeHilos implements IServidor {

	// Puerto para escuchar las conexiones entrantes.
	protected int listenPort = 3000;
	
	// Número máximo de conexiones de clientes activas.
	protected int maxConnections;
	
	// Socket servidor que acepta las conexiones de los clientes.
	protected ServerSocket server;
	
	
	protected static List<Socket> pool = new LinkedList<Socket>();
	protected static int poolSize = 2;
	protected static boolean stopThreads = false; 
	
	protected Thread [] workers;
	
	/**
	 * 
	 * @param listenPort
	 * @param maxConnections
	 */
	public ServidorPilaDeHilos(){
		setUpHandlers();
	}
	
	public void setUpHandlers() {
		workers = new Thread[poolSize];
		for (int i = 0; i < poolSize; i++){
			System.out.println("FileRequestPooledHandler. SetUp handler_ " + i);
			workers[i] = new Thread( new PooledWorkerThread(), "Handler_" + i);
			workers[i].start();
		}
	}
	
	
	public void aceptarConexiones() throws BindException, IOException {
		
		try{
			System.out.println("ServidorPilaHilos.aceptarConexiones");

			server = new ServerSocket();
			server.setReuseAddress(true);
			server.bind(new InetSocketAddress(Inet4Address.getLocalHost(), listenPort), 5);
			
			Socket incomingConnection = null;
			while (true) {
				System.out.println("ServidorPilaHilos.aceptarConexiones.Waiting");
				incomingConnection = server.accept();
				handleConnection(incomingConnection);
			}
		}
		catch(BindException be){
			be.printStackTrace();	
			throw be;
		}
		catch(IOException ioe){
			ioe.printStackTrace();		
			throw ioe;
		}
	}
	
	
	
	/**
	 * Interacciona con el socket cliente para enviarle los contenidos
	 * del fichero solicitado.
	 * Es un método bloqueante. Retorna cuando ha terminado de enviar el
	 * fichero.
	 * @param incomingConection
	 */
	
	public void handleConnection(Socket incomingConnection){
		synchronized (pool){
			System.out.println("ServidorPilaHilos.aceptarConexiones.Adding request");
			pool.add(pool.size(), incomingConnection);
			pool.notifyAll();
		}
	}
	
	
	
    class PooledWorkerThread implements Runnable {
    	
    	IHandler handler;
    	
    	public void run(){
    		while (!stopThreads){
    			// Wait on the connection pool 
    			synchronized (pool){
    				while (pool.isEmpty()){
    					try{
    						pool.wait();
    						// Call to wait releases the lock.
    						// This allows other code synchronized on the pool object
    						// to acquire the pool.
    					}
    					catch(InterruptedException ie){
    						ie.printStackTrace();
    						return;
    					}
    				}
    				// Take connection from the pool.
    				System.out.println("ServidorPilaHilos.aceptarConexiones.Taking request");
    				Socket connection = (Socket) pool.remove(0);
    	    		handler = new FileRequestHandler(connection);
    			}
    			System.out.println("ServidorPilaHilos.aceptarConexiones.Doing service");
    			// handle the connection once the pool has one.
    			handler.doService();
    		}    		
    	}
    }
	
    
	public void shutdown(){
		stopThreads = true;
		for (int i = 0; i < poolSize; i++){
			if (workers[i] != null) workers[i].interrupt();
		}
		try {
			if (server != null) {
				server.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void finalize(){
		shutdown();
	}
	

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception{
		ServidorPilaDeHilos servidor = new ServidorPilaDeHilos();
        servidor.aceptarConexiones();
	}
	
}
